namespace EmailLogProviderCS
{
    using System;
    using Microsoft.SqlServer.Dts.Runtime;
    using System.Net.NetworkInformation;
    using System.Text.RegularExpressions;
    using System.IO;
    using System.Net.Mail;
    using System.Net;

    // The LogProviderType property is required but not used.
    //  The custom log provider will not appear in the list
    //  if a LogProviderType is not provided.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces"), DtsLogProvider(DisplayName = "Custom log provider for mail messages (CS)",
    Description = "Writes log entries for events to a mail message",
    LogProviderType = "Custom")]
    public class EmailLogProviderCS : LogProviderBase
    {
        #region  Variables and Constants

        // Constants.
        private const string SUBCOMPONENT = "EmailLogProviderCS";
        private const string PACKAGE_END_EVENT = "PackageEnd";

        // Variables.
        private Microsoft.SqlServer.Dts.Runtime.Connections _connections;
        private Microsoft.SqlServer.Dts.Runtime.IDTSInfoEvents _events;
        private string _configString;
        private string[] _configArgs;
        private string _smtpServer;
        private string _messageTo;
        private string _messageFrom;
        private string _messageFormat;
        private HtmlLogWriterCS _htmlLogWriter;
        private TextLogWriterCS _textLogWriter;
        private MemoryStream _messageStream;
        private StreamWriter _messageStreamwriter;

        // Status flags.
        private bool _fireEventsAgain;
        private bool _loggingAlreadyStarted;
        private bool _packageHasEnded;

        #endregion

        #region Initialize and Validate

        public override void InitializeLogProvider(Microsoft.SqlServer.Dts.Runtime.Connections connections, Microsoft.SqlServer.Dts.Runtime.IDTSInfoEvents events, Microsoft.SqlServer.Dts.Runtime.ObjectReferenceTracker refTracker)
        {
            // Cache object references for later use.
            this._connections = connections;
            this._events = events;

            this._fireEventsAgain = true;
        }

        public override Microsoft.SqlServer.Dts.Runtime.DTSExecResult Validate(Microsoft.SqlServer.Dts.Runtime.IDTSInfoEvents events)
        {
            if (events == null)
            {
                throw new ArgumentNullException("events");
            }

            // This regular expression is just one of many variations that can be found
            //  on the Internet for validating email addresses.
            const string EMAIL_ADDRESS_PATTERN = "^[a-zA-Z][\\w\\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]\\.[a-zA-Z][a-zA-Z\\.]*[a-zA-Z]$";

            Ping smtpServerPing;
            PingReply smtpServerReply;
            Regex emailAddressTest;

            this._configString = this.ConfigString;

            // Configuration string should contain 4 arguments.
            // Validate function needs to validate:
            // 1. Has a config string been provided?
            // 2. Does the config string contain 4 values?
            // 3. Does SMTP server name appear to be valid?
            // 4. Is To address in correct format?
            // 5. Is From address in correct format?
            // 6. Is message format argument "text" or "html"?

            // Validation test 1: Has a config string been provided?
            if (this._configString.Length == 0)
            {
                events.FireError(0, SUBCOMPONENT, "The log provider did not receive configuration information.", String.Empty, 0);
                return DTSExecResult.Failure;
            }

            // Validation test 2: Does the config string contain 4 values?
            this._configArgs = this._configString.Split(new char[] { ';' });
            if (this._configArgs.Length != 4)
            {
                events.FireError(0, SUBCOMPONENT, "The log provider configuration does not contain the 4 expected arguments.", String.Empty, 0);
            }

            // Extract the 4 values from config string.
            this._smtpServer = this._configArgs[0];
            this._messageTo = this._configArgs[1];
            this._messageFrom = this._configArgs[2];
            this._messageFormat = this._configArgs[3].ToLower(System.Globalization.CultureInfo.InvariantCulture);

            // Validation test 3. Does SMTP server name appear to be valid? Ping the server.
            smtpServerPing = new Ping();
            smtpServerReply = smtpServerPing.Send(this._smtpServer);
            if (smtpServerReply.Status != IPStatus.Success)
            {
                events.FireError(
                    0, 
                    SUBCOMPONENT, 
                    "The specified SMTP server '" + this._smtpServer + "' cannot be reached." + smtpServerReply.Status.ToString(), 
                    String.Empty, 
                    0);
            }

            // Validation test 4: Is To address in correct format?
            emailAddressTest = new Regex(EMAIL_ADDRESS_PATTERN);
            if (!emailAddressTest.IsMatch(this._messageTo))
            {
                events.FireError(
                    0, 
                    SUBCOMPONENT,
                    "The To address '" + this._messageTo + "' does not appear to be in the proper format.", 
                    String.Empty, 
                    0);
            }

            // Validation test 5: Is From address in correct format?
            if (!emailAddressTest.IsMatch(this._messageFrom))
            {
                events.FireError(
                    0, 
                    SUBCOMPONENT, 
                    "The To address '" + this._messageFrom + "' does not appear to be in the proper format.", 
                    String.Empty, 
                    0);
            }

            // Validation test 6: Is message format argument "text" or "html"?
            if (this._messageFormat != "text" && this._messageFormat != "html")
            {
                events.FireError(
                    0, 
                    SUBCOMPONENT, 
                    "The message format must be either 'text' or 'html'.", 
                    String.Empty, 
                    0);
            }

            return DTSExecResult.Success;
        }

        #endregion

        #region OpenLog and CloseLog

        public override void OpenLog()
        {
            string subComponentInfo = SUBCOMPONENT + "-OpenLog (" + this._messageFormat + ")";

            if (this._fireEventsAgain)
            {
                this._events.FireInformation(0, subComponentInfo, "Opening email log.", String.Empty, 0, ref this._fireEventsAgain);
            }

            if (!this._loggingAlreadyStarted)
            {
                this._messageStream = new MemoryStream();
                this._messageStreamwriter = new StreamWriter(this._messageStream);

                switch (this._messageFormat)
                {
                    case "text":
                        this._textLogWriter = new TextLogWriterCS();
                        this._textLogWriter.InitializeLogWriter(this._connections, this._events, SUBCOMPONENT, this._fireEventsAgain);
                        this._textLogWriter.OpenLogText(this._messageStreamwriter);
                        break;

                    case "html":
                        this._htmlLogWriter = new HtmlLogWriterCS();
                        this._htmlLogWriter.InitializeLogWriter(this._connections, this._events, SUBCOMPONENT, this._fireEventsAgain);
                        this._htmlLogWriter.OpenLogHtml(this._messageStreamwriter);
                        break;
                }

                this._loggingAlreadyStarted = true;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public override void CloseLog()
        {
            string messageSubject;
            bool isBodyHtml = false;
            StreamReader messageStreamReader;
            string messageBody;

            string subComponentInfo = SUBCOMPONENT + "-CloseLog ("
                + this._messageFormat + ")";

            if (this._fireEventsAgain)
            {
                this._events.FireInformation(
                    0, 
                    subComponentInfo, 
                    "Closing email log.", 
                    String.Empty, 
                    0, 
                    ref this._fireEventsAgain);
            }

            // The following IF test is necessary because
            //  OpenLog and CloseLog are called multiple times.
            //  Omitting this test results in multiple mail messages.

            if (this._packageHasEnded)
            {
                messageSubject = "SSIS Package Log (" + this._messageFormat 
                    + ") - " + DateTime.Now.ToString();

                switch (this._messageFormat)
                {
                    case "text":
                        this._textLogWriter.CloseLogText();
                        isBodyHtml = false;
                        break;

                    case "html":
                        this._htmlLogWriter.CloseLogHtml();
                        isBodyHtml = true;
                        break;
                }

                try
                {
                    this._messageStreamwriter.Flush();

                    this._messageStream.Position = 0;
                    messageStreamReader = new StreamReader(this._messageStream);
                    messageBody = messageStreamReader.ReadToEnd();

                    this._messageStreamwriter.Close();
                    messageStreamReader.Close();

                    this.SendMailMessage(
                        this._messageTo,
                        this._messageFrom, 
                        messageSubject, 
                        messageBody, 
                        isBodyHtml,
                        this._smtpServer);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    this._events.FireError(
                        0, 
                        subComponentInfo, 
                        ex.Message, 
                        String.Empty, 
                        0);
                }
            }
        }

        #endregion

        #region Send Mail Message

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        private void SendMailMessage(string SendTo, string From, string Subject, string Body, bool IsBodyHtml, string Server)
        {
            MailMessage myMessage;
            SmtpClient mySmtpClient;

            myMessage = new MailMessage(SendTo, From, Subject, Body);
            myMessage.IsBodyHtml = IsBodyHtml;

            mySmtpClient = new SmtpClient(Server);
            mySmtpClient.Credentials = CredentialCache.DefaultNetworkCredentials;
            mySmtpClient.Send(myMessage);

            myMessage.Dispose();
        }

        #endregion

        #region Write individual log entries

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public override void Log(string logEntryName, string computerName, string operatorName, string sourceName, string sourceID, string executionID, string messageText, DateTime startTime, DateTime endTime, int dataCode, byte[] dataBytes)
        {
            string subComponentInfo = SUBCOMPONENT + "-Log (" + this._messageFormat + ")";

            if (this._fireEventsAgain)
            {
                this._events.FireInformation(0, subComponentInfo, "Writing email log entry for " + logEntryName, String.Empty, 0, ref this._fireEventsAgain);
            }

            try
            {
                switch (this._messageFormat)
                {
                    case "text":
                        this._textLogWriter.LogText(logEntryName, computerName, operatorName, sourceName, sourceID, executionID, messageText, startTime, endTime, dataCode, dataBytes);
                        break;

                    case "html":
                        this._htmlLogWriter.LogHtml(logEntryName, computerName, operatorName, sourceName, sourceID, executionID, messageText, startTime, endTime, dataCode, dataBytes);
                        break;
                }

                if (logEntryName == PACKAGE_END_EVENT)
                {
                    this._packageHasEnded = true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                this._events.FireError(0, subComponentInfo, ex.Message, String.Empty, 0);
            }
        }

        #endregion
    }
}